﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.EntityClient;
using System.Data.Objects;
using System.Linq;
using System.Reflection;
using System.Text;

namespace FR.CodeVisualizer.ObjectSource
{
    internal class ContextHelper
    {
        internal static string GetSQLCommands(ObjectContext context)
        {
            StringBuilder sql = new StringBuilder();
            foreach (DbCommand command in ContextHelper.GetContextCommands(context))
            {
                sql.Append("------------------------------------");
                sql.AppendLine();
                sql.Append("-- Command");
                sql.AppendLine();
                sql.Append("------------------------------------");
                sql.AppendLine();

                foreach (DbParameter parameter in command.Parameters)
                {
                    System.Data.SqlClient.SqlParameter r = new System.Data.SqlClient.SqlParameter(parameter.DbType.ToString(), parameter.Value);

                    sql.AppendLine(string.Format("DECLARE {0} {1}{2};", parameter.ParameterName, r.SqlDbType, GetTypeLength(r.SqlDbType)));
                    sql.AppendLine(string.Format("SET {0} = '{1}';", parameter.ParameterName, parameter.Value));
                }
                sql.AppendLine();
                sql.Append(command.CommandText);
                sql.AppendFormat("{0}GO{0}{0}", Environment.NewLine);
            }

            return sql.ToString();
        }

        public static string GetTypeLength(SqlDbType type)
        {
            switch (type)
            {
                case SqlDbType.Decimal:
                    return "(38,38)";
                case SqlDbType.Binary:
                case SqlDbType.Char:
                case SqlDbType.NChar:
                    return "(8000)";
                case SqlDbType.NVarChar:
                case SqlDbType.VarBinary:
                case SqlDbType.VarChar:
                    return "(MAX)";
                default:
                    return "";
            }
        }

        private static IEnumerable<DbCommand> GetContextCommands(ObjectContext context)
        {
            const string EntityAssemblyName = "System.Data.Entity, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";

            var entityAssemly = Assembly.Load(EntityAssemblyName);

            var updateTranslatorType = entityAssemly.GetType("System.Data.Mapping.Update.Internal.UpdateTranslator");
            var functionUpdateCommandType = entityAssemly.GetType("System.Data.Mapping.Update.Internal.FunctionUpdateCommand");
            var dynamicUpdateCommandType = entityAssemly.GetType("System.Data.Mapping.Update.Internal.DynamicUpdateCommand");

            var ctorParams = new object[]
            {
                context.ObjectStateManager,
                ((EntityConnection)context.Connection).GetMetadataWorkspace(),
                (EntityConnection)context.Connection,
                context.CommandTimeout
            };

            var updateTranslator = Activator.CreateInstance(
                updateTranslatorType,
                BindingFlags.NonPublic | BindingFlags.Instance,
                null,
                ctorParams,
                null);

            MethodInfo produceCommandsMethod = updateTranslatorType
                .GetMethod("ProduceCommands", BindingFlags.Instance | BindingFlags.NonPublic);
            var updateCommands = produceCommandsMethod.Invoke(updateTranslator, null) as IEnumerable;

            foreach (object o in updateCommands)
            {
                if (functionUpdateCommandType.IsInstanceOfType(o))
                {
                    FieldInfo mdbCommandField = functionUpdateCommandType.GetField(
                        "m_dbCommand", BindingFlags.Instance | BindingFlags.NonPublic);

                    yield return mdbCommandField.GetValue(o) as DbCommand;
                }
                else if (dynamicUpdateCommandType.IsInstanceOfType(o))
                {
                    MethodInfo createCommandMethod = dynamicUpdateCommandType.GetMethod(
                        "CreateCommand", BindingFlags.Instance | BindingFlags.NonPublic);

                    var methodParams = new object[]
                    {
                        updateTranslator,
                        new Dictionary<int, object>()
                    };

                    yield return createCommandMethod.Invoke(o, methodParams) as DbCommand;
                }
            }
        }
    }
}
